﻿using System;
using System.Collections.Generic;
using PacMan3D.Objects;
using System.IO;
using PacMan3D.Utils;
using PacMan3D.Exceptions;
using PacMan3D.Sounds;
using Tao.OpenGl;
using PacMan3D.Camera;

namespace PacMan3D.IO
{
    public class Maze
    {
        private sealed class MazeElementTypes
        {
            public const string Wall = "1";
            public const string Food = ".";
            public const string BigFood = ":";
            public const string PacMan = "P";
            public const string Ghost = "G";
            public const string Life = "L";
            public const string None = "o";
        }

        private bool PacManModel3ds;

        private int GhostCounter;
        public int Width {get; private set;}
        public int Height { get; private set; }
        public float BlockSize { get; private set; }
        public PacMan PacMan { get; private set; }
        public Platform Platform { get; private set; }
        public List<ICollidable> FoodObjects { get; private set; }
        public List<ICollidable> BigFoodObjects { get; private set; }
        public List<ICollidable> GhostObjects { get; private set; }
        public List<ICollidable> LifeObjects { get; private set; }
        public List<ICollidable> WallBoxObjects { get; private set; }
        public List<ICollidable> EmptyBoxObjects { get; private set; }

        private float[] ShadowMatrix;

        public Maze()
        {
            this.GhostCounter = 0;

            this.FoodObjects = new List<ICollidable>();
            this.BigFoodObjects = new List<ICollidable>();
            this.GhostObjects = new List<ICollidable>();
            this.LifeObjects = new List<ICollidable>();
            this.WallBoxObjects = new List<ICollidable>();
            this.EmptyBoxObjects = new List<ICollidable>();
        }

        public void LoadMap(String file)
        {
            FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
            StreamReader streamReader = new StreamReader(fileStream);

            try
            {
                ///
                /// obtém as dimensões do labirinto
                /// 
                string[] array = streamReader.ReadLine().Split(' ');
                Height = Int32.Parse(array[0]);
                Width = Int32.Parse(array[1]);

                ///
                /// se o pacman é para ser desenhado pelo 3ds
                /// 
                BlockSize = Convert.ToSingle(streamReader.ReadLine());

                ///
                /// obtém o tamanho do bloco
                /// 
                PacManModel3ds = (((int)Convert.ToSingle(streamReader.ReadLine())) == 1);

                ///
                /// cria o chão do labirinto
                /// 
                Platform = new Platform(Height, Width, BlockSize);

                int widthCounter = 0;
                int heightCounter = this.Height;
                EmptyBox nEmptyBox;

                ///
                /// obtém a distribuição dos objectos no mapa
                /// 
                while (!streamReader.EndOfStream)
                {
                    --heightCounter;

                    string buffer = streamReader.ReadLine();

                    for (int i = 0; i < buffer.Length; i++)
                    {
                        string current = buffer[i].ToString();

                        switch (current)
                        {
                            case MazeElementTypes.Wall:
                                WallBox nWallBox = new WallBox(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.WallBoxObjects.Add(nWallBox);
                                break;

                            case MazeElementTypes.Food:
                                Food nFood = new Food(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.FoodObjects.Add(nFood); 
                                nEmptyBox = new EmptyBox(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.EmptyBoxObjects.Add(nEmptyBox);
                                break;

                            case MazeElementTypes.BigFood:
                                BigFood nBigFood = new BigFood(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.BigFoodObjects.Add(nBigFood); 
                                nEmptyBox = new EmptyBox(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.EmptyBoxObjects.Add(nEmptyBox);
                                break;

                            case MazeElementTypes.PacMan:
                                this.PacMan = new PacMan(this.PacManModel3ds, new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                nEmptyBox = new EmptyBox(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.EmptyBoxObjects.Add(nEmptyBox);
                                break;

                            case MazeElementTypes.Ghost:
                                this.GhostCounter++;
                                Ghost nGhost = new Ghost(this.GhostCounter, new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.GhostObjects.Add(nGhost);
                                Food newFood = new Food(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.FoodObjects.Add(newFood); 
                                nEmptyBox = new EmptyBox(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.EmptyBoxObjects.Add(nEmptyBox);
                                break;

                            case MazeElementTypes.Life:
                                Life nLife = new Life(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.LifeObjects.Add(nLife);
                                nEmptyBox = new EmptyBox(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.EmptyBoxObjects.Add(nEmptyBox); 
                                break;
                            
                            case  MazeElementTypes.None:
                                nEmptyBox = new EmptyBox(new Point3D(widthCounter * BlockSize, 0.0f, heightCounter * BlockSize), BlockSize);
                                this.EmptyBoxObjects.Add(nEmptyBox);
                                break;

                            default:
                                throw new Exception("Unknown Maze Element Type");
                        }
                        ++widthCounter;
                    }
                    
                    if (widthCounter != this.Width)
                        throw new Exception("Wrong Maze Width");

                    widthCounter = 0;
                }

                if (heightCounter != 0)
                    throw new Exception("Wrong Maze Height");
            }
            catch (Exception ex)
            {
                streamReader.Close();
                fileStream.Close();
                throw new MazeFileException(ex.Message, ex);
            }

            return;
        }

        /// <summary>
        /// Quando um fantasma apanha o PacMan.
        /// </summary>
        public void GhostCollision()
        {
            SoundController.Instance().PlaySound(SoundController.SoundEventType.PACMAN_GHOST);
            this.PacMan.LooseLife();
        }

        /// <summary>
        /// Quando o PacMan apanha uma Vida.
        /// </summary>
        /// <param name="life"></param>
        public void LifeCollision(Life life)
        {
            SoundController.Instance().PlaySound(SoundController.SoundEventType.PACMAN_LIFE);
            this.PacMan.GetLife();
            this.LifeObjects.Remove(life);
        }

        /// <summary>
        /// Quando o PacMan apanha uma Comida.
        /// </summary>
        /// <param name="food"></param>
        public void FoodCollision(Food food)
        {
            SoundController.Instance().PlaySound(SoundController.SoundEventType.PACMAN_FOOD);
            this.FoodObjects.Remove(food);
            this.PacMan.EatFood();
        }

        /// <summary>
        /// Quando o PacMan apanha uma Comida da Grande.
        /// </summary>
        /// <param name="bigfood"></param>
        public void BigFoodCollision(BigFood bigfood)
        {
            SoundController.Instance().PlaySound(SoundController.SoundEventType.PACMAN_BIG_FOOD);
            this.BigFoodObjects.Remove(bigfood);
            this.PacMan.EatBigFood();
        }

        /// <summary>
        /// Método que obtém a comida restante.
        /// </summary>
        /// <returns></returns>
        public int GetFoodLeft()
        {
            return (this.FoodObjects.Count + this.BigFoodObjects.Count);
        }
        
        /// <summary>
        /// Método qur contém as instruções para animar o jogo.
        /// </summary>
        public void AnimeGame(CameraObserver currentObserver)
        {
            if (this.PacMan.IsAlive)
            {
                if (this.PacMan.IsEasyMode)
                {
                    this.PacMan.UpdatePosition();
                }

                if (this.PacMan.IsBorning)
                {
                    this.PacMan.IncreaseAge();
                }

                foreach (Ghost ghost in this.GhostObjects)
                {
                    ghost.UpdatePosition();
                    ghost.StopEatPacMacAnime();
                }
            }
            else
            {
                this.PacMan.PlayDyingAnime();
                foreach (Ghost ghost in this.GhostObjects)
                {
                    ghost.PlayEatPacMacAnime();
                }
            }
        }

        /// <summary>
        /// Step2: Create a shadow-projection matrix based
        /// </summary>
        /// <param name="lightPosition"></param>
        public void CalculateShadowMatrix(float[] lightPosition)
        {
            this.ShadowMatrix = Shadow.BuildShadowMatrix(lightPosition, this.Platform.GetFPlane());
        }


        public void DrawObjects()
        {
            ///
            /// Draw the Platform
            /// 
            this.Platform.DrawObject();

            ///
            /// Draw the BigFood Shadow
            ///
            foreach (BigFood bigFood in this.BigFoodObjects)
                bigFood.DrawShadow(this.ShadowMatrix);

            ///
            /// Draw the BigFood
            ///
            foreach (BigFood bigFood in this.BigFoodObjects)
                bigFood.DrawObject();

            ///
            /// Draw the Walls
            ///
            foreach (WallBox wall in this.WallBoxObjects)
                wall.DrawObject();

            ///
            /// Draw the Food
            ///
            foreach (Food food in this.FoodObjects)
                food.DrawObject();

            ///
            /// Draw the Ghost
            ///
            foreach (Ghost ghost in this.GhostObjects)
                ghost.DrawObject();

            ///
            /// Draw the Life
            /// 
            foreach (Life life in this.LifeObjects)
                life.DrawObject();

            ///
            /// Draw the PacMan
            /// 
            this.PacMan.DrawObject();
        }

        public bool GameOver()
        {
            ///
            /// Game Over Condition - PacMan Loose
            /// 
            if ((this.PacMan.Lifes == 0) && this.PacMan.IsDyingAnimeFinished())
            {
                SoundController.Instance().PlaySound(SoundController.SoundEventType.GAME_OVER_LOOSE_1);
                return true;
            }

            ///
            /// Game Over Condition - PacMan Win
            ///
            if (GetFoodLeft() == 0)
            {
                SoundController.Instance().PlaySound(SoundController.SoundEventType.GAME_OVER_WIN_1);
                return true;
            }

            return false;
        }

        public bool IsPacManWinner()
        {
            ///
            /// Game Over - PacMan Loose
            /// 
            if (this.PacMan.Lifes == 0)
                return false;

            ///
            /// Game Over Condition - PacMan Win
            ///
            if (GetFoodLeft() == 0)
                return true;

            return false;
        }
    }
}
